﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImportMedia.aspx.cs" company="Prolific Notion">
//   (c) Copyright 2010 Simon Dingley t/a Prolific Notion, http://prolificnotion.co.uk
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// --------------------------------------------------------------------------------------------------------------------
// Distrbuted under the MIT License
// --------------------------------------------------------------------------------------------------------------------

// (c) Copyright 2010 Simon Dingley t/a Prolific Notion, http://prolificnotion.co.uk

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies or substantial portions of 
// the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// --------------------------------------------------------------------------------------------------------------------
#endregion

namespace ProlificNotion.Umbraco.ImportMedia.umbraco.dialogs
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    using global::umbraco;
    using global::umbraco.BasePages;
    using global::umbraco.BusinessLogic;
    using global::umbraco.cms.businesslogic.media;
    using global::umbraco.cms.businesslogic.property;

    using Image = System.Drawing.Image;

    /// <summary>Defines the ImportMedia page type.</summary>
    public partial class ImportMedia : Page
    {
        #region Constants and Fields

        /// <summary>The path to the directory containing the icons</summary>
        private const string IconDir = "~/umbraco/images/importMedia/";

        /// <summary>
        /// The path to the folder icon
        /// </summary>
        private const string IconFolder = IconDir + "folder.png";

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the folder mapping.
        /// </summary>
        /// <value>The folder mapping.</value>
        private Dictionary<string, int> FolderMapping { get; set; }

        #endregion

        #region Methods

        /// <summary>Handles the SelectedNodeChanged event of the DirTreeView control.</summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void DirTreeView_SelectedNodeChanged(object sender, EventArgs e)
        {
            this.DirTreeView.SelectedNode.Checked = !this.DirTreeView.SelectedNode.Checked;
        }

        /// <summary>Handles the TreeNodeCheckChanged event of the DirTreeView control.</summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.TreeNodeEventArgs"/> instance containing the event data.</param>
        protected void DirTreeView_TreeNodeCheckChanged(object sender, TreeNodeEventArgs e)
        {
            this.ToggleChildren(e.Node, e.Node.Checked);
        }

        /// <summary>Raises the <see cref="E:System.Web.UI.Control.Init"/> event to initialize the page.</summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            this.FolderMapping = new Dictionary<string, int>();
            this.DirTreeView.TreeNodeCheckChanged += this.DirTreeView_TreeNodeCheckChanged;
        }

        /// <summary>Handles the Load event of the Page control.</summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.Page.IsPostBack)
            {
                this.PopulateTreeView();
            }
        }

        /// <summary>Handles the Click event of the importButton control.</summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void ImportButtonClick(object sender, EventArgs e)
        {
            this.Import(null);
        }

        /// <summary>Deletes the folder when it cointains no files or folders.</summary>
        /// <param name="folderPath">The folder path.</param>
        private void DeleteFolderWhenEmpty(string folderPath)
        {
            var folder = new DirectoryInfo(folderPath);
            if (folder.GetFileSystemInfos().Length == 0)
            {
                // Folder is empty
                folder.Delete();
            }
        }

        /// <summary>Generates the thumbnail.</summary>
        /// <param name="media">The media.</param>
        /// <remarks>Contributed by Richard Soeteman</remarks>
        private void GenerateThumbnail(Media media)
        {
            var maxWidthHeight = Config.ThumbnailMaxWidthHeight;

            if (media == null)
            {
                return;
            }

            var umbracoFilename = media.getProperty("umbracoFile").Value.ToString();
            var umbracoExtension = media.getProperty("umbracoExtension").Value.ToString();
            var thumbnailFilename = umbracoFilename.Replace("." + umbracoExtension, "_thumb.jpg");

            using (
                var fs = new FileStream(
                    this.Server.MapPath(umbracoFilename), FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var image = Image.FromStream(fs);

                // - Get the image dimension
                var fileWidth = image.Width;
                var fileHeight = image.Height;
                fs.Close();

                // Generate thumbnail
                var fx = fileWidth / (float)maxWidthHeight;
                var fy = fileHeight / (float)maxWidthHeight;

                // must fit in thumbnail size
                var f = Math.Max(fx, fy); // if (f < 1) f = 1;
                var widthTh = (int)Math.Round(fileWidth / f);
                var heightTh = (int)Math.Round(fileHeight / f);

                // fixes for empty width or height
                if (widthTh == 0)
                {
                    widthTh = 1;
                }

                if (heightTh == 0)
                {
                    heightTh = 1;
                }

                // Create new image with best quality settings
                var bp = new Bitmap(widthTh, heightTh);
                var g = Graphics.FromImage(bp);
                g.SmoothingMode = SmoothingMode.HighQuality;
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;

                // Copy the old image to the new and resized
                var rect = new Rectangle(0, 0, widthTh, heightTh);
                g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);

                // Copy metadata
                var codecs = ImageCodecInfo.GetImageEncoders();
                ImageCodecInfo codec = null;
                for (var i = 0; i < codecs.Length; i++)
                {
                    if (codecs[i].MimeType.Equals("image/jpeg"))
                    {
                        codec = codecs[i];
                    }
                }

                // Set compresion ratio to 90%
                var ep = new EncoderParameters();
                ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);

                // Save the new image
                bp.Save(this.Server.MapPath(thumbnailFilename), codec, ep);
                bp.Dispose();
                g.Dispose();
            }
        }

        /// <summary>Gets the directories.</summary>
        /// <param name="subDirs">The sub directories to add to the node.</param>
        /// <param name="parent">The Parent.</param>
        private void GetDirectories(IEnumerable<DirectoryInfo> subDirs, TreeNode parent)
        {
            DirectoryInfo[] subSubDirs;

            foreach (var subDir in subDirs)
            {
                var node = new TreeNode(subDir.Name, subDir.FullName)
                    { Value = subDir.FullName, ImageUrl = IconFolder, Expanded = false, ShowCheckBox = true };

                this.GetFiles(subDir, node);

                parent.ChildNodes.Add(node);

                subSubDirs = subDir.GetDirectories();
                if (subSubDirs.Length != 0)
                {
                    this.GetDirectories(subSubDirs, node);
                }
            }
        }

        /// <summary>Gets the file icon.</summary>
        /// <param name="extension">The extension.</param>
        /// <returns>The path to the file icon</returns>
        private static string GetFileIcon(string extension)
        {
            // - Remove the dot if it exists
            extension = extension.TrimStart('.').ToLower();

            // - Get ImageFileTypes from the Umbraco Settings
            var imgExt = new ArrayList(UmbracoSettings.ImageFileTypes.Split(','));

            if (imgExt.Contains(extension))
            {
                return String.Concat(IconDir, "picture.png");
            }

            return File.Exists(String.Concat(IconDir, extension)) ? String.Concat(extension, ".png") : String.Concat(IconDir, "file.png");
        }

        /// <summary>Gets the files in a given directory.</summary>
        /// <param name="dir">The directory to get files from.</param>
        /// <param name="parent">The parent node to append files nodes to.</param>
        private void GetFiles(DirectoryInfo dir, TreeNode parent)
        {
            foreach (var file in dir.GetFiles())
            {
                if (file != null)
                {
                    var node = new TreeNode(file.Name, file.FullName)
                        {
                            ImageUrl = GetFileIcon(file.Extension),
                            Expanded = false,
                            ShowCheckBox = true,
                            Checked = parent.Checked
                        };

                    parent.ChildNodes.Add(node);
                }
            }
        }

        /// <summary>Gets the parent folder.</summary>
        /// <param name="folderName">Name of the folder.</param>
        /// <returns>The parent id</returns>
        private int GetParent(string folderName)
        {
            return this.FolderMapping.ContainsKey(folderName) ? this.FolderMapping[folderName] : int.Parse(this.Request.QueryString["id"]);
        }

        /// <summary>Imports the media into the specified parent media item.</summary>
        /// <param name="parent">The parent media item.</param>
        private void Import(TreeNode parent)
        {
            TreeNodeCollection tnc;

            if (parent == null)
            {
                tnc = this.DirTreeView.Nodes;
            }
            else
            {
                tnc = parent.ChildNodes;
            }

            foreach (TreeNode node in tnc)
            {
                if (node.Checked)
                {
                    if (File.Exists(node.Value))
                    {
                        if (node.Parent != null)
                        {
                            this.ImportFile(node.Value, node.Parent.Value);
                        }
                    }
                    else
                    {
                        this.ImportFolder(node.Value, (node.Parent == null) ? String.Empty : node.Parent.Value);
                    }
                }

                this.Import(node);
            }

            this.instructionLabel.CssClass = "success";
            this.instructionLabel.Text = "Media file(s) imported successfully";
            this.DirTreeView.Visible = false;
            this.importButton.Visible = false;
        }

        /// <summary>Creates the media node.</summary>
        /// <param name="sourceFile">The source file to import.</param>
        /// <param name="parentPath">The parent Path.</param>
        private void ImportFile(string sourceFile, string parentPath)
        {
            var file = new FileInfo(sourceFile);
            var filename = Path.GetFileName(sourceFile).Substring(0, Path.GetFileName(sourceFile).LastIndexOf("."));
            var extension = Path.GetExtension(sourceFile).TrimStart('.').ToLower();
            var imageFileExtensions = UmbracoSettings.ImageFileTypes.ToLower();
            var parentNodeId = this.GetParent(parentPath);
            var mediaFileName = Path.GetFileName(sourceFile);

            // - Set a default MediaType for imports
            var mt = MediaType.GetByAlias("File");

            // - Check if we are importing an image and if so set the appropriate MediaType
            if (imageFileExtensions.IndexOf(extension) > 0)
            {
                mt = MediaType.GetByAlias("Image");
            }

            // - Create the new media item
            Media m = Media.MakeNew(filename, mt, UmbracoEnsuredPage.CurrentUser, parentNodeId);

            Property umbracoFile = m.getProperty("umbracoFile");

            // - Try setting some of the properties for the file
            try
            {
                umbracoFile.Value = String.Format("/media/{0}/{1}", umbracoFile.Id, mediaFileName);
                m.getProperty("umbracoBytes").Value = file.Length;
                if (sourceFile != null)
                {
                    m.getProperty("umbracoExtension").Value = Path.GetExtension(sourceFile).Replace(".", string.Empty);

                    // - If we are importing an image try and set some of the image properties
                    if (mt.Alias == "Image")
                    {
                        // Check if image and then get sizes, make thumb and update database
                        if (m.getProperty("umbracoWidth") != null && m.getProperty("umbracoHeight") != null &&
                            imageFileExtensions.IndexOf(extension) > -1)
                        {
                            int fileWidth;
                            int fileHeight;

                            using (var fs = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                            {
                                var image = Image.FromStream(fs);

                                // - Get the image dimension
                                fileWidth = image.Width;
                                fileHeight = image.Height;
                                fs.Close();

                                // - Set the image dimensions
                                m.getProperty("umbracoWidth").Value = fileWidth.ToString();
                                m.getProperty("umbracoHeight").Value = fileHeight.ToString();

                                image.Dispose();
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // - If the process fails at this point we need to remove the media node
                // - or we end up with a node without a file
                m.delete();
                Log.Add(LogTypes.Error, parentNodeId, String.Concat("Error importing media item : ", ex.Message));
            }

            // - Create the directory on the server for the file
            Directory.CreateDirectory(this.Server.MapPath("~/media") + "\\" + umbracoFile.Id);

            // - Move the file from the import location to the new directory
            this.MoveFile(sourceFile, umbracoFile.Id);

            // - Save the Media item
            m.Save();

            if (mt.Alias == "Image")
            {
                // Generate thumbnails
                this.GenerateThumbnail(m);
            }
        }

        /// <summary>Imports the folder.</summary>
        /// <param name="folderPath">The folder path.</param>
        /// <param name="parentPath">The parent path.</param>
        private void ImportFolder(string folderPath, string parentPath)
        {
            var info = new DirectoryInfo(folderPath);

            // - Only import folder if it contains files else whats the point?
            if (info.GetFiles().Length > 0 || parentPath == String.Empty)
            {
                var folderName = info.Name;
                var mt = MediaType.GetByAlias("Folder");
                var m = Media.MakeNew(folderName, mt, UmbracoEnsuredPage.CurrentUser, this.GetParent(parentPath));

                // Add the mapping to the dictionary so we can determine the Folder ID in a later stage
                this.FolderMapping.Add(folderPath, m.Id);
            }
        }

        /// <summary>Moves the file.</summary>
        /// <param name="source">The source.</param>
        /// <param name="target">The Id of the media item.</param>
        private void MoveFile(string source, int target)
        {
            var f = new FileInfo(source);

            try
            {
                var folderPath = f.DirectoryName;

#if DEBUG

                // - Copy the file to the new media item directory
                f.CopyTo(this.Server.MapPath("~/media/" + target + "/" + Path.GetFileName(f.Name)));
#else
    
                // Move the file to the new media item directory
                f.MoveTo(Server.MapPath("~/media/" + target.ToString() + "/" + Path.GetFileName(f.Name)));
                DeleteFolderWhenEmpty(folderPath);
#endif
            }
            catch (Exception ex)
            {
                // Display an error message
                this.instructionLabel.CssClass = "error";
                this.instructionLabel.Text = ex.Message;
                
                // Log the exception
                Log.Add(LogTypes.Error, target, ex.ToString());
            }
        }

        /// <summary>Populates the tree view.</summary>
        private void PopulateTreeView()
        {
            foreach (var dir in Config.ImportFolders)
            {
                var info = new DirectoryInfo(this.Server.MapPath(dir));
                if (!info.Exists)
                {
                    continue;
                }

                var rootNode = new TreeNode(info.Name)
                    {
                        Value = info.FullName,
                        ImageUrl = IconFolder,
                        Expanded = false,
                        ShowCheckBox = true,
                        SelectAction = TreeNodeSelectAction.SelectExpand
                    };

                this.GetDirectories(info.GetDirectories(), rootNode);
                this.GetFiles(info, rootNode);

                this.DirTreeView.Nodes.Add(rootNode);
            }
        }

        /// <summary>Recursive .</summary>
        /// <param name="node">The tree node.</param>
        /// <param name="nodeIsChecked">If set to <c>true</c> [node is checked].</param>
        private void ToggleChildren(TreeNode node, bool nodeIsChecked)
        {
            foreach (TreeNode child in node.ChildNodes)
            {
                child.Checked = nodeIsChecked;
                if (child.ChildNodes.Count > 0)
                {
                    // Has children loop again
                    this.ToggleChildren(child, nodeIsChecked);
                }
            }
        }

        #endregion
    }
}